<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>从 FileInputStream.read() 到 Windows api ReadFile() | jingxiao's space</title><meta name="author" content="jingxiao"><meta name="copyright" content="jingxiao"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="Java 使用 IO 流进行文件的读写应该是非常基本的操作了，在之前的一个练习项目中，有个浏览器上传图片即时回显功能用到了这个步骤，即将上传到服务器的图片读出来写回到浏览器响应里，最终展示在浏览器页面上： 1234567891011...省略    FileInputStream fileInputStream &#x3D; new FileInputStream(new File(basePath + n">
<meta property="og:type" content="article">
<meta property="og:title" content="从 FileInputStream.read() 到 Windows api ReadFile()">
<meta property="og:url" content="https://jingxiao-yz.github.io/2023/05/09/aboutstream/index.html">
<meta property="og:site_name" content="jingxiao&#39;s space">
<meta property="og:description" content="Java 使用 IO 流进行文件的读写应该是非常基本的操作了，在之前的一个练习项目中，有个浏览器上传图片即时回显功能用到了这个步骤，即将上传到服务器的图片读出来写回到浏览器响应里，最终展示在浏览器页面上： 1234567891011...省略    FileInputStream fileInputStream &#x3D; new FileInputStream(new File(basePath + n">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://jingxiao-yz.github.io/img/default_cover.jpg">
<meta property="article:published_time" content="2023-05-09T12:09:14.000Z">
<meta property="article:modified_time" content="2023-06-08T15:39:52.590Z">
<meta property="article:author" content="jingxiao">
<meta property="article:tag" content="keywordssssssss">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://jingxiao-yz.github.io/img/default_cover.jpg"><link rel="shortcut icon" href="/img/favicon.ico"><link rel="canonical" href="https://jingxiao-yz.github.io/2023/05/09/aboutstream/index.html"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  dateSuffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false,
  percent: {
    toc: true,
    rightside: false,
  }
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: '从 FileInputStream.read() 到 Windows api ReadFile()',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-06-08 23:39:52'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
    win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
      const link = document.createElement('link')
      link.rel = 'stylesheet'
      link.href = url
      if (id) link.id = id
      link.onerror = reject
      link.onload = link.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        link.onload = link.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(link)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="jingxiao's space" type="application/atom+xml">
</head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="/img/head.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">3</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">0</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">0</div></a></div><hr/></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('/img/default_cover.jpg')"><nav id="nav"><span id="blog-info"><a href="/" title="jingxiao's space"><span class="site-name">jingxiao's space</span></a></span><div id="menus"><div id="toggle-menu"><a class="site-page" href="javascript:void(0);"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">从 FileInputStream.read() 到 Windows api ReadFile()</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2023-05-09T12:09:14.000Z" title="发表于 2023-05-09 20:09:14">2023-05-09</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-06-08T15:39:52.590Z" title="更新于 2023-06-08 23:39:52">2023-06-08</time></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="从 FileInputStream.read() 到 Windows api ReadFile()"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><p>Java 使用 IO 流进行文件的读写应该是非常基本的操作了，在之前的一个练习项目中，有个浏览器上传图片即时回显功能用到了这个步骤，即将上传到服务器的图片读出来写回到浏览器响应里，最终展示在浏览器页面上：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">...省略</span><br><span class="line">    <span class="type">FileInputStream</span> <span class="variable">fileInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="keyword">new</span> <span class="title class_">File</span>(basePath + name));</span><br><span class="line">    <span class="type">ServletOutputStream</span> <span class="variable">outputStream</span> <span class="operator">=</span> response.getOutputStream();</span><br><span class="line">...省略</span><br><span class="line">    <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span>];</span><br><span class="line">    <span class="keyword">while</span> ((len = fileInputStream.read(bytes)) != -<span class="number">1</span>) &#123;</span><br><span class="line">        outputStream.write(bytes, <span class="number">0</span>, len);</span><br><span class="line">        outputStream.flush();</span><br><span class="line">    &#125;</span><br><span class="line">...省略</span><br></pre></td></tr></table></figure>

<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>一开始对 <code>while ((len = fileInputStream.read(bytes)) != -1)</code> 这行代码是有疑问的，按我的理解：</p>
<ul>
<li><code>read</code>方法可以不接收参数，返回输入流的下一个字节数据；</li>
<li>也可以接收一个<code>byte[] b</code>，数组<code>b</code>称之为缓冲区，将输入流的数据存到<code>b</code>中，同时返回读取的字节数量</li>
</ul>
<p>而我当时的疑问是：</p>
<ul>
<li>如果<code>read</code>方法不接受参数还好，当未读取任何字节时，返回<code>-1</code>，表示文件末尾，此时变量<code>len</code>被赋值<code>-1</code>，循环结束；</li>
<li>但是当传入<code>byte</code>数组时，<code>len</code>应该是读入的数据长度呀，就算读到末尾没数据了，长度也应该时<code>0</code>呀，长度怎么会是<code>-1</code>，那不就死循环了嘛，但是实际代码确实可以正常运行的。</li>
</ul>
<p>于是去看<code>read(byte[] b)</code>具体是怎么实现计数的，定位到<code>FileInputStream.java</code>中的<code>read</code>方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Reads up to &#123;<span class="doctag">@code</span> b.length&#125; bytes of data from this input</span></span><br><span class="line"><span class="comment"> * stream into an array of bytes. This method blocks until some input</span></span><br><span class="line"><span class="comment"> * is available.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>      b   &#123;<span class="doctag">@inheritDoc</span>&#125;</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span>     the total number of bytes read into the buffer, or</span></span><br><span class="line"><span class="comment"> *             &#123;<span class="doctag">@code</span> -1&#125; if there is no more data because the end of</span></span><br><span class="line"><span class="comment"> *             the file has been reached.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span>     IOException  if an I/O error occurs.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">read</span><span class="params">(<span class="type">byte</span>[] b)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">comp</span> <span class="operator">=</span> Blocker.begin();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> readBytes(b, <span class="number">0</span>, b.length);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        Blocker.end(comp);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>好吧，在注释第<code>7</code>行写了，大意是 “返回读到<code>buffer</code>里字节的数量，如果到文件末尾没数据了，返回<code>-1</code>”，这么看，就是咱不熟悉这个方法的返回值呀。<br>为了求证这个事情，自己也简单测试了一下：<br>先准备一个文件<code>in.txt</code>，里面有<code>12</code>个字母数据<code>aaabbbcccddd</code>，每个字母占1个字节空间，同时设置一个大小为<code>10</code>的byte数组<code>b</code>来做缓冲，</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">FileInputStream</span> <span class="variable">fileInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;D:\\Desktop\\in1.txt&quot;</span>)) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">            <span class="type">byte</span> b[] = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">10</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">while</span> ((len = fileInputStream.read(b)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;第&quot;</span> + j + <span class="string">&quot;次读入数量len=&quot;</span> + len);</span><br><span class="line">                j++;</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(<span class="string">&quot;最后读入数量len=&quot;</span> + len);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>来看看变量<code>len</code>的控制台输出：<br><img src="/2023/05/09/aboutstream/1.png" alt="1"><br>果然，在读取完文件数据后，<code>len</code>最后的值为<code>-1</code>，</p>
<p>由于<code>FileInputStream</code>继承自抽象类<code>InputStream</code>，所以<code>FileInputStream</code>里 重写了 <code>read()</code>，其实就是调用了另一个方法： <code>readBytes()</code>，又定位到这个方法，还是一个 <code>native</code> 方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Reads a subarray as a sequence of bytes.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>     b the data to be written</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>     off the start offset in the data</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>     len the number of bytes that are written</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span>    IOException If an I/O error has occurred.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">readBytes</span><span class="params">(<span class="type">byte</span>[] b, <span class="type">int</span> off, <span class="type">int</span> len)</span> <span class="keyword">throws</span> IOException;</span><br></pre></td></tr></table></figure>

<p>其实看到前面注释里关于返回值的说明，关于<code>-1</code>疑问也算消除了，不过我比较关心的是：</p>
<h2 id="那这个native方法readBytes-到底是怎么计算所读字节的个数？"><a href="#那这个native方法readBytes-到底是怎么计算所读字节的个数？" class="headerlink" title="那这个native方法readBytes()到底是怎么计算所读字节的个数？"></a>那这个native方法readBytes()到底是怎么计算所读字节的个数？</h2><p>因为<code>native</code>方法是Java调用非Java代码的接口，使得Java虚拟机中的Java程序可以调用本地应用&#x2F;或库，在本地IDE里面无法定位到这个函数的声明或定义，它位于 <strong>openjdk</strong> 源码中。<br>在 <strong>GitHub</strong> 上找到 <strong>openjdk</strong> 项目的代码，我在目录：<code>jdk/src/java.base/share/native/libjava/</code> 下找到了 <strong>FileInputStream.c</strong> 源文件（可能因后期项目更新而路径变化），里面有 <code>readBytes()</code> 的具体实现：<code>Java_java_io_FileInputStream_readBytes()</code>，关于这个名字的样子说下，它遵守 <strong>JNI</strong>（Java native interface）方法命名规则：</p>
<ul>
<li>Java_前缀</li>
<li>全限定的类名</li>
<li>下划线（_）分隔符</li>
<li>增加第一参数JNIEnv* env</li>
<li>增加第二个参数jobject</li>
<li>其他参数按类型映射</li>
<li>返回值按类型映射</li>
</ul>
<p>就是说：<code>readBytes()</code>在<code>java.io.FileInputStream</code>这个类里面，类名加上前缀，点号换成下划线，最后加上Java中 方法自己的名字。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">JNIEXPORT jint JNICALL</span><br><span class="line"><span class="title function_">Java_java_io_FileInputStream_readBytes</span><span class="params">(JNIEnv *env, jobject this,</span></span><br><span class="line"><span class="params">        jbyteArray bytes, jint off, jint len)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> readBytes(env, this, bytes, off, len, fis_fd);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>关于这个<code>native</code>方法的几个参数类型，搜索了一下</p>
<ul>
<li><p><code>JNIEnv</code>，全称是<code>Java native interface environment</code>（Java本地接口环境），所有的本地方法的第一个参数都是指向JNIEnv结构的，即这个方法接收的指针 <code>env</code> 指向了本地方法的一个函数表，该函数表中的每一个成员指向了一个JNI函数，本地方法通过JNI函数来访问JVM中的数据结构，具体如图（侵删）：<img src="/2023/05/09/aboutstream/jni.png" alt="jni"></p>
</li>
<li><p><code>jobject</code> 第二个参数通常是<code>jobject</code>或<code>jclass</code>，对应Java中的<code>Object</code>和<code>Class</code>，当所声明的native方法是静态方法时，对应参数是jclass，因为静态方法不依赖对象实例，而依赖于类，所以传递一个jclass类型。相反，如果声明的native方法是非静态方法时，那么对应参数是jobject。具体到这里，这个native方法对应的是FileInputStream里面的read方法，它是非静态的，依赖我们实例化一个输入流对象，并调用read()才存在的。</p>
</li>
<li><p><code>jbyteArray</code> 和 <code>jint</code> 对应的就是Java里面的 <code>byteArray</code> 和 <code>int</code> 类型。除了第1、2个参数固定，后面的参数就是对应的Java方法的参数，我们在Java方法中传入了一个byte数组和两个int数字，所以后面还有这三个参数。</p>
</li>
</ul>
<p>但是这个函数的实现是调用的另一个 <code>C函数readBytes()</code>，（这里不得不说GitHub真好用！）选中<code>readBytes</code>这个函数名，Github会搜索项目所有文件中的相同字符，于是，我在目录：<code>jdk/src/java.base/share/native/libjava/</code>下的 <strong>io_util.c</strong> 文件中找到了这个函数的定义实现：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line">jint <span class="comment">//返回值类型 jint对应int</span></span><br><span class="line">readBytes(JNIEnv *env, jobject this, jbyteArray bytes,</span><br><span class="line">          jint off, jint len, jfieldID fid)<span class="comment">//jfieldID 可用于获取类中变量的标识，然后通过此标识来读写变量</span></span><br><span class="line">&#123;</span><br><span class="line">    jint nread; <span class="comment">//最终的返回值</span></span><br><span class="line">    <span class="type">char</span> stackBuf[BUF_SIZE]; <span class="comment">//建一个char数组</span></span><br><span class="line">    <span class="type">char</span> *buf = <span class="literal">NULL</span>; <span class="comment">//char指针</span></span><br><span class="line">    FD fd;</span><br><span class="line">    <span class="comment">// FD 是File Descriptor，文件描述符。文件描述符是一个 非负整数，本质上是一个索引值。</span></span><br><span class="line">    <span class="comment">// 在src/java.base/unix/native/libjava/io_util_md.h中有定义# define FD jint，所以FD相当于是个int类型</span></span><br><span class="line">    <span class="comment">// 后续read/write 这个文件时，只需要用这个文件描述符来标识该文件，将其作为参数传入read/write</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (IS_NULL(bytes)) &#123;<span class="comment">//传入的byte数组为空，返回-1（也就是存不了呗）</span></span><br><span class="line">        JNU_ThrowNullPointerException(env, <span class="literal">NULL</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (outOfBounds(env, off, len, bytes)) &#123; <span class="comment">//参数越界</span></span><br><span class="line">        JNU_ThrowByName(env, <span class="string">&quot;java/lang/IndexOutOfBoundsException&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (len == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (len &gt; BUF_SIZE) &#123; <span class="comment">//len大于BUF_SIZE</span></span><br><span class="line">        buf = <span class="built_in">malloc</span>(len); <span class="comment">//重新分配一块len大小的内存空间，并把buf指向这块地</span></span><br><span class="line">        <span class="keyword">if</span> (buf == <span class="literal">NULL</span>) &#123; <span class="comment">//没分到，说明内存不够了</span></span><br><span class="line">            JNU_ThrowOutOfMemoryError(env, <span class="literal">NULL</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123; <span class="comment">//一切正常的话，buf指向stackBuf[]</span></span><br><span class="line">        buf = stackBuf;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    fd = getFD(env, this, fid); <span class="comment">//通过 this 和 fid 获取文件描述符fd</span></span><br><span class="line">    <span class="keyword">if</span> (fd == <span class="number">-1</span>) &#123;</span><br><span class="line">        JNU_ThrowIOException(env, <span class="string">&quot;Stream Closed&quot;</span>);</span><br><span class="line">        nread = <span class="number">-1</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123; <span class="comment">//fd一般为非负整数</span></span><br><span class="line">        nread = IO_Read(fd, buf, len); <span class="comment">//  调用IO_Read(), 传入 fd 来操作文件，所读字节数赋给nread</span></span><br><span class="line">        <span class="keyword">if</span> (nread &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            (*env)-&gt;SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf); <span class="comment">//将本地的数组数据拷贝到了 Java 端的数组 bytes</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (nread == <span class="number">-1</span>) &#123;</span><br><span class="line">            JNU_ThrowIOExceptionWithLastError(env, <span class="string">&quot;Read error&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123; <span class="comment">/* EOF */</span></span><br><span class="line">            nread = <span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (buf != stackBuf) &#123;</span><br><span class="line">        <span class="built_in">free</span>(buf); <span class="comment">//释放buf指向的空间</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> nread; <span class="comment">//返回所读字节数</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的代码我尽量注释了，涉及到 <strong>文件描述符</strong> 的部分我可能有理解错误，但影响不大。可以看见，在第<code>40</code>行，最关键的读文件步骤，是调用了 <code>IO_Read()</code> 函数，好吧，继续找，在头文件：<code>jdk/src/java.base/windows/native/libjava/</code><strong>io_util_md.h</strong> 中，有定义<code>#define IO_Read handleRead</code>，也就是说 <code>IO_Read</code> 就是 <code>handleRead</code>，那继续顺藤摸瓜找 <code>handleRead</code> 的定义实现，很明显应该在这个头文件对应的c代码文件里面，也就是 <code>jdk/src/java.base/windows/native/libjava/</code><strong>io_util_md.c</strong> 中，果然找到了它的实现，代码如下：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">JNIEXPORT</span><br><span class="line">jint</span><br><span class="line"><span class="title function_">handleRead</span><span class="params">(FD fd, <span class="type">void</span> *buf, jint len)</span> <span class="comment">//句柄读，句柄是Windows用来标识被应用程序所建立或使用的对象的唯一整数</span></span><br><span class="line">&#123;</span><br><span class="line">    DWORD read = <span class="number">0</span>; <span class="comment">// 所读字节数量，最终返回值，取地址传入ReadFile()中操作</span></span><br><span class="line">    BOOL result = <span class="number">0</span>;</span><br><span class="line">    HANDLE h = (HANDLE)fd; <span class="comment">//将 文件描述符 转为 句柄</span></span><br><span class="line">    <span class="keyword">if</span> (h == INVALID_HANDLE_VALUE) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    result = ReadFile(h,          <span class="comment">/* File handle to read */</span></span><br><span class="line">                      buf,        <span class="comment">/* address to put data */</span></span><br><span class="line">                      len,        <span class="comment">/* number of bytes to read */</span></span><br><span class="line">                      &amp;read,      <span class="comment">/* number of bytes read */</span></span><br><span class="line">                      <span class="literal">NULL</span>);      <span class="comment">/* no overlapped struct */</span></span><br><span class="line">    <span class="keyword">if</span> (result == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">int</span> error = GetLastError();</span><br><span class="line">        <span class="keyword">if</span> (error == ERROR_BROKEN_PIPE) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>; <span class="comment">/* EOF */</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> (jint)read;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>结果发现，最终的读文件操作还是调的一个函数 <code>ReadFile()</code>，虽然有注释，即返回第 <strong>4</strong> 个参数指针指向的变量，但是这个函数在 <strong>openjdk</strong> 项目文件中是没有定义的，怎么办呢，尝试搜索了一下这个函数，居然是 <strong>Windows</strong> 应用开发的一个函数，贴个地址吧 <a target="_blank" rel="noopener" href="https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-readfile">https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-readfile</a></p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">BOOL <span class="title function_">ReadFile</span><span class="params">(</span></span><br><span class="line"><span class="params">  [in]                HANDLE       hFile,</span></span><br><span class="line"><span class="params">  [out]               LPVOID       lpBuffer,</span></span><br><span class="line"><span class="params">  [in]                DWORD        nNumberOfBytesToRead,</span></span><br><span class="line"><span class="params">  [out, optional]     LPDWORD      lpNumberOfBytesRead,</span></span><br><span class="line"><span class="params">  [in, out, optional] LPOVERLAPPED lpOverlapped</span></span><br><span class="line"><span class="params">)</span>;</span><br></pre></td></tr></table></figure>

<p>关键看官方文档有关第<strong>4</strong>个参数的解释：<img src="/2023/05/09/aboutstream/readfileexplain.png" alt="213"><br>大意就是，这个位置的参数是个指针，指向 接收读取的字节数的变量xxx，<code>ReadFile()</code>会初始化这个xxx的值为 0。</p>
<p>回到<strong>openjdk</strong>里面的<code>handleRead()</code>，第4个位置传入的指针就是变量<code>read</code>的地址，经过调用<strong>Windows</strong>的<code>ReadFile()</code>一顿黑箱操作，就把将<code>read</code>的值变为了 读取的字节数量，最后将<code>read</code>返回给上一层。</p>
<p>我捋了下这个调用链：<br>fileInputstream.<strong>read</strong>(byte[] b) 【FileInputStream.java】—&gt; <strong>readBytes()</strong> 【FileInputStream.java，native】—JNI—&gt; <strong>Java_java_io_FileInputStream_readBytes()</strong> 【FileInputStream.c】—&gt; <strong>readBytes()</strong> 【io_util.c】—&gt; <strong>IO_Read()</strong> 【io_util_md.h】—&gt; <strong>handleRead()</strong> 【io_util_md.c】—&gt; <strong>ReadFile()</strong> 【fileapi.h，Windows】</p>
<p><strong>这里总算是到头了，毕竟Windows的代码不开源，绕一大圈结果还是不知道它怎么计算读取的字节数的</strong></p>
<p>不过回过头来看，JNI这个框架，它的目的就是<strong>使得Java虚拟机中的Java程序可以调用本地应用或库</strong>，在Openjdk项目代码里面查调用链的时候，发现有平行的windows、Linux、macosx等文件夹目录存在，里面都是相应平台上native方法的实现代码，究其原因就是有些程序可能会涉及到调用系统的api，所以分开来，大家各司其职。</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://jingxiao-yz.github.io">jingxiao</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://jingxiao-yz.github.io/2023/05/09/aboutstream/">https://jingxiao-yz.github.io/2023/05/09/aboutstream/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://jingxiao-yz.github.io" target="_blank">jingxiao's space</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"></div><div class="post_share"><div class="social-share" data-image="/img/default_cover.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2023/06/06/offer100/" title="剑指offer"><img class="cover" src="/img/default_cover.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">剑指offer</div></div></a></div><div class="next-post pull-right"><a href="/2023/05/04/LeetCode-Hot-100/" title="LeetCode_Hot_100"><img class="cover" src="/img/default_cover.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">LeetCode_Hot_100</div></div></a></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="/img/head.jpg" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">jingxiao</div><div class="author-info__description">记录一哈</div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">3</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">0</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">0</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/jingxiao-yz"><i class="fab fa-github"></i><span>戳我</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">我来啦！</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%B5%B7%E5%9B%A0"><span class="toc-number">1.</span> <span class="toc-text">起因</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%82%A3%E8%BF%99%E4%B8%AAnative%E6%96%B9%E6%B3%95readBytes-%E5%88%B0%E5%BA%95%E6%98%AF%E6%80%8E%E4%B9%88%E8%AE%A1%E7%AE%97%E6%89%80%E8%AF%BB%E5%AD%97%E8%8A%82%E7%9A%84%E4%B8%AA%E6%95%B0%EF%BC%9F"><span class="toc-number">2.</span> <span class="toc-text">那这个native方法readBytes()到底是怎么计算所读字节的个数？</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/2023/06/06/offer100/" title="剑指offer"><img src="/img/default_cover.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="剑指offer"/></a><div class="content"><a class="title" href="/2023/06/06/offer100/" title="剑指offer">剑指offer</a><time datetime="2023-06-06T10:30:42.000Z" title="发表于 2023-06-06 18:30:42">2023-06-06</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2023/05/09/aboutstream/" title="从 FileInputStream.read() 到 Windows api ReadFile()"><img src="/img/default_cover.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="从 FileInputStream.read() 到 Windows api ReadFile()"/></a><div class="content"><a class="title" href="/2023/05/09/aboutstream/" title="从 FileInputStream.read() 到 Windows api ReadFile()">从 FileInputStream.read() 到 Windows api ReadFile()</a><time datetime="2023-05-09T12:09:14.000Z" title="发表于 2023-05-09 20:09:14">2023-05-09</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2023/05/04/LeetCode-Hot-100/" title="LeetCode_Hot_100"><img src="/img/default_cover.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="LeetCode_Hot_100"/></a><div class="content"><a class="title" href="/2023/05/04/LeetCode-Hot-100/" title="LeetCode_Hot_100">LeetCode_Hot_100</a><time datetime="2023-05-04T12:14:51.000Z" title="发表于 2023-05-04 20:14:51">2023-05-04</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By jingxiao</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.umd.min.js"></script><div class="js-pjax"></div><canvas class="fireworks" mobile="true"></canvas><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/fireworks.min.js"></script><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="true" data-click="false"></script><script defer="defer" id="fluttering_ribbon" mobile="true" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-fluttering-ribbon.min.js"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>